#coding=utf-8
#
# 说明文字大部分是这个网址翻译的：
# https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.txt
# 注意：
# 1、测试的pcapng文件里有些类型的包没有出现，下面的脚本执行过程中有一些未覆盖到。
# 2、一些option因为时间关系没有解析。后续有时间再补
# 3、运行版本python3.4+

from enum import Enum
from optparse import OptionParser

def round_to_4byte(input_integer_value):
    """
    32位向上对齐。例如输入值=3，则返回值=4。又比如0=>0, 5=>8
    :param input_integer_value: 需要向上对齐的数字
    :return:
    """
    return (input_integer_value + 3) & (~3)

class BlockTypeCode(Enum):
    BTS_SectionHeaderBlock = 0x0A0D0D0A
    BTS_InterfaceDescriptionBlock = 0x00000001
    BTS_PacketBlock_Obsolete = 0x00000002
    BTS_SimplePacketBlock = 0x00000003
    BTS_NameResolutionBlock = 0x00000004
    BTS_InterfaceStatisticsBlock = 0x00000005
    BTS_EnhancedPacketBlock = 0x00000006
    BTS_DecryptionSecretsBlock = 0x0000000A
    BTS_CustomBlockWhichCanBeCopied = 0x00000BAD
    BTS_CustomBlockWhichShouldNotBeCopied = 0x40000BAD

class PcapngBlockHeader:
    """
    3.1.  General Block Structure
    捕获文件以块的形式组织，这些块相互堆叠以形成文件。 所有块共享一个通用格式，如图1所示。.
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                          Block Type                           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                      Block Total Length                       |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    /                          Block Body                           /
    /          /* 可变长度, 但是必须32位（4字节）对齐*/             /
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                      Block Total Length                       |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    Figure 1: Basic block structure.
    这些字段具有以下含义：
    Block Type (32 bits): 标识块的唯一值。最高有效位(MSB)等于1的值保留供本地使用。 They allow to save private data
        to the file and to extend the file format. The list of currently defined types can be found in Appendix B
    Block Total Length: 此块的总大小，以字节为单位。例如一个没有正文的块的长度是12个字节。
    Block Body: 块的内容。
    Block Total Length: 此块的总大小，以字节为单位。该字段和复制前值放在尾部以方便文件的反向访问。
    这种在所有块之间共享的结构使得处理文件和跳过不需要或未知的块变得容易。 一些块可以在内部包含
    其他块（嵌套块）。 一些块是必需的，即如果它们不存在则转储文件无效，其他块是可选的。
    The General Block Structure allows defining other blocks if needed. A parser that does non understand them
    can simply ignore their content.
    """
    block_type = -1
    block_total_length = -1
    _is_big_endian = True

    def __init__(self, is_big_endian):
        self._is_big_endian = is_big_endian

    def read_from_file_object(self, file_object):
        #open("file.pcapng", "rb") with file_object
        return_value = False
        try:
            string_from_bytes_endian = "big"
            if not self._is_big_endian:
                string_from_bytes_endian = "little"
            data = file_object.read(4)
            # 一般而言 big: 网络字节序, little:主机字节序
            self.block_type = int.from_bytes(data, string_from_bytes_endian) # 'big' or 'little'
            data = file_object.read(4)
            self.block_total_length = int.from_bytes(data, string_from_bytes_endian)
            print("block type:%x, block total length:%x"%(self.block_type, self.block_total_length))
            return_value = True
        except Exception as e:
            print(e)
        return return_value

class PcapngOptions:
    """
    3.5.  Options
    所有block的body部分都可以嵌入可选字段。可选字段可用于插入一些在读取数据时可能有用的信息，但对于数据包处理来
    说并不是真正需要的。因此每个工具都可以读取可选字段（如果有）的内容，或者跳过其中的一些甚至全部。
    一次跳过所有可选字段很简单，因为大多数块由具有固定格式的第一部分和可选的第二个部分组成。 因此，块长度字段（
    Block Length，出现在通用块结构中，参见第 2.1 节）可用于跳过所有内容，直到下一个块。
    选项是一系列类型 - 长度 - 值字段，每个字段包含一个值：
    Option Type (2 bytes): 它包含指定当前 TLV 记录类型的代码。最高有效位等于 1 的选项类型保留供本地使用；因此，
    不能保证所用的code在所有捕获文件（由其他应用程序生成）中是唯一的。In case of vendor-specific extensions
    that have to be identified uniquely, vendors must request an Option Code whose MSB is equal to zero.
    Option Length (2 bytes): 它包含以下“选项值”字段的实际长度，不含填充字节。
    Option Value (variable length): 它包含给定选项的值，与32位边界对齐（4字节对齐）。该字段的实际长度（即排除
    填充字节后）由选项长度字段指定。
    选项可能会重复多次（例如一个接口有多个关联的IP地址）TODO: mention for each option, if it can/shouldn't
    appear more than one time. The option list is terminated by a Option which uses the special
    'End of Option' code (opt_endofopt).
    The format of the optional fields is shown in Figure 7.
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |      Option Code              |         Option Length         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    /                       Option Value                            /
    /         /* variable length, 4字节对齐（32 bits）*/            /
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    /                                                               /
    /                 . . . other options . . .                     /
    /                                                               /
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Option Code == opt_endofopt  |  Option Length == 0          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     Figure 7: Options format.
    以下code可以出现在任何可选字段中：
    Name          Code    Length    Description                              Example(s)
    opt_endofopt	0       0         It delimits the end of the optional fields. This block cannot be repeated within a given list of options.
    opt_comment   1      variable	 包含与当前块关联的注释的 UTF-8 字符串。	"This packet is the beginning of all of our problems" / "Packets 17-23 showing a bogus TCP retransmission, as reported in bugzilla entry 1486!" / "Captured at the southern plant" / "I've checked again, now it's working ok" / ...
    """
    _option_buffer = None
    _option_length = 0

    def __init__(self, option_buffer, option_length):
        self._option_buffer = option_buffer
        self._option_length = option_length

    def parse_option_for_section_header_block(self, section_endian):
        """
        解析并打印所有用于section header block的options。
        除了第2.5节中定义的选项外，还有以下选项在此块中有效：
        名字          代码  长度  说明                                         例子
        shb_hardware  2     可变  UTF-8字符串，描述创建此section的硬件。     "x86 Personal Computer" / "Sun Sparc Workstation" / ...
        shb_os        3     可变  UTF-8字符串，创建此section的操作系统名称。 "Windows XP SP2" / "openSUSE 10.2" / ...
        shb_userappl  4     可变  UTF-8字符串，创建此section的应用程序名称。 "dumpcap V0.99.7" / ...
        :section_endian: "big"或者"little"表示大端还是小端机器
        :return:
        """
        remain_option_length = self._option_length
        cursor = 0
        opt_endofopt = 0
        pnp = {2:"opt_shb_hardware", 3:"opt_shb_os", 4:"opt_shb_userappl"}
        while remain_option_length != 0:
            option_code = int.from_bytes(self._option_buffer[cursor : cursor+2], section_endian)
            if option_code == opt_endofopt:
                print("match end of option list op code.")
                break
            option_length = int.from_bytes(self._option_buffer[cursor+2 : cursor+4], section_endian)
            option_value = self._option_buffer[cursor+4 : cursor+4+option_length]
            if option_code in pnp:
                print("option:%s, option_length:%d(%d)" % (pnp[option_code], option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
            else:
                print("option:%d, option_length:%d(%d)"%(option_code, option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
            cursor += (4 + round_to_4byte(option_length))

    def parse_option_for_interface_description_block(self, section_endian):
        pnp = {2: "if_name", 3: "if_description", 4: "if_IPv4addr",
               5: "if_IPv6addr", 6: "if_MACaddr", 7: "if_EUIaddr",
               8: "if_speed", 9: "if_tsresol", 10: "if_tzone",
               11: "if_filter", 12: "if_os", 13: "if_fcslen",
               14: "if_tsoffset"}
        remain_option_length = self._option_length
        cursor = 0
        opt_endofopt = 0
        while remain_option_length != 0:
            option_code = int.from_bytes(self._option_buffer[cursor: cursor + 2], section_endian)
            if option_code == opt_endofopt:
                print("match end of option list op code.")
                break
            option_length = int.from_bytes(self._option_buffer[cursor + 2: cursor + 4], section_endian)
            option_value = self._option_buffer[cursor + 4: cursor + 4 + option_length]
            if option_code in pnp:
                print("option:%s, option_length:%d(%d)" % (
                pnp[option_code], option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
            else:
                print("option:%d, option_length:%d(%d)" % (option_code, option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
            cursor += (4 + round_to_4byte(option_length))


class PcapngSecionHeaderBlock:
    """
    Section Header Block是必要数据。它用于标识捕获转储文件的一个section的开头。 Section Header Block不包含数据，
    而是标识逻辑相关的块（接口、数据包）列表。
      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +---------------------------------------------------------------+
    0 |                   Block Type = 0x0A0D0D0A                     |
      +---------------------------------------------------------------+
    4 |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |                      Byte-Order Magic                         |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    12|          Major Version        |         Minor Version         |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    16|                                                               |
      |                          Section Length                       |
      |                                                               |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    24/                                                               /
      /                      Options (variable)                       /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                      Block Total Length                       |
      +---------------------------------------------------------------+
      Section Header Block 的块类型是与4个字符的字符串“\r\n\n\r”对应的整数（0x0A0D0D0A）。使用此值有两个原因：
      1，此数字用于检测文件是否已通过FTP或 HTTP 从一台机器传输到另一台机器并进行了不适当的ASCII转换。在这种情况
      下，此字段的值将不同于标准值 ("\r\n\n\r")，并且读取方可以检测到可能已损坏的文件。
      2，该值是回文的，因此无论节的字节序如何，读取方都能够识别节标题块。字节顺序是通过读取Byte-Order Magic来识
      别的，它位于块类型之后的8个字节。
      Block Total Length: total size of this block，以字节为单位。
      Byte-Order Magic: 幻数，其值为十六进制数 0x1A2B3C4D。这个数字可以用来区分这个section是小端机还是大端机上生成的。
      Major Version：当前格式主版本号。当前值为1。如果格式的更改导致工具无法读取旧格式存档，则该值应该更改。
      Minor Version：当前格式次版本号。当前值为0。如果格式的更改导致工具无法读取旧格式存档，则该值应该更改。
      Section Length:此section的长度，以字节为单位，不包含本Section Header Block的长度，此字段可用于跳过该section
      ，以便在大文件中更快地导航。Section Length为-1(0xFFFFFFFFFFFFFFFF)表示未指定节的大小，跳过该节的唯一方法是
      解析其包含的块（block）。请注意如果此字段有效（即不是 -1），则其值始终与32位对齐，因为所有块(block)都与32位
      边界对齐。此外在访问此字段时应特别小心：由于文件中所有块的对齐方式都是32位，因此不能保证此字段与64位边界对齐
      。这可能在64位工作站上发生问题。
      Options: 可选的选项列表（根据第2.5节中定义的规则格式化）
      添加新的块类型或选项不一定需要更改Major或Minor版本号，因为不知道块类型或选项的代码可以跳过它；仅当跳过块或选项
      会导致无法工作时，才需要更改Minor版本号。
      除了第2.5节中定义的选项外，还有以下选项在此块中有效：
      名字          代码  长度  说明                                         例子
      shb_hardware  2     可变  UTF-8字符串，描述创建此section的硬件。     "x86 Personal Computer" / "Sun Sparc Workstation" / ...
      shb_os        3     可变  UTF-8字符串，创建此section的操作系统名称。 "Windows XP SP2" / "openSUSE 10.2" / ...
      shb_userappl  4     可变  UTF-8字符串，创建此section的应用程序名称。 "dumpcap V0.99.7" / ...
    """
    magic = -1
    version_major = -1
    version_minor = -1
    section_length = -1 # 如值为-1表示未知
    _endian = ""
    incorrect_format = False
    block_type = 0x0A0D0D0A
    block_total_length = -1
    block_total_length_with_padding = -1

    def read_from_file_object(self, file_object):
        return_value = False
        try:
            # 这里先不计算，因为还不知道字节序，下面要根据magic的值确定是否要转换。
            # file_offset + 8 (函数调用前还有个0x0A0D0D0A头)
            block_total_length_raw = file_object.read(4)

            # file_offset + 12
            data = file_object.read(4)
            self.magic = int.from_bytes(data, "big")
            if 0x1A2B3C4D != self.magic:
                self._endian = "little"
                self.magic = int.from_bytes(data, "little")
                if 0x1A2B3C4D != self.magic:
                    self.incorrect_format = True
                    return False
            else:
                self._endian = "big"
            self.block_total_length = int.from_bytes(block_total_length_raw, self._endian)
            self.block_total_length_with_padding = round_to_4byte(self.block_total_length)
            print("block type:%#x, block total length:%#x(with padding: %#x)"%(self.block_type,
                                                                               self.block_total_length,
                                                                               self.block_total_length_with_padding))
            # file_offset + 14
            data = file_object.read(2)
            self.version_major = int.from_bytes(data, self._endian)
            # file_offset + 16
            data = file_object.read(2)
            self.version_minor = int.from_bytes(data, self._endian)
            print("File version: %d.%d"%(self.version_major, self.version_minor))
            # file_offset + 24
            data = file_object.read(8)
            self.section_length = int.from_bytes(data, self._endian)
            print("section length : %#x" % self.section_length)

            data_raw_options = file_object.read(self.block_total_length_with_padding - 24 - 4)
            po = PcapngOptions(data_raw_options, self.block_total_length_with_padding - 24 - 4)
            po.parse_option_for_section_header_block(self._endian)

            data = file_object.read(4)
            block_total_length_backward = int.from_bytes(data, self._endian)
            print("Block total length backward:%#x"%block_total_length_backward)
            if block_total_length_backward != self.block_total_length:
                print("错误，前后块长度不一致。")
                return False

            return_value = True
        except Exception as e:
            print(e)
        return return_value

    def get_endian(self):
        return self._endian


class PcapngInterfaceDescriptionBlock:
    """4.2.  Interface Description Block (必需)
    网络接口描述块是必要的。 需要此块来细化进行捕获的网络接口的特征。为了将捕获的数据正确关联到相应的接口，必须在使用
    它的任何其他块之前定义接口描述块；因此，该块通常紧跟在Section Header Block之后。
    接口描述块仅在它所属的section内有效。The structure of a Interface Description Block is shown in Figure 9.
        0                   1                   2                   3
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
       +---------------------------------------------------------------+
     0 |                    Block Type = 0x00000001                    |
       +---------------------------------------------------------------+
     4 |                      Block Total Length                       |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     8 |           LinkType            |           Reserved            |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    12 |                            SnapLen                            |
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    16 /                                                               /
       /                      Options (variable)                       /
       /                                                               /
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       |                      Block Total Length                       |
       +---------------------------------------------------------------+
     Figure 9: Interface Description Block format.
    The meaning of the fields is:
        (1)Block Type: The block type of the Interface Description Block is 1.
        (2)Block Total Length: total size of this block, as described in Section 2.1.
        (3)LinkType: 定义此接口的链路层类型的值。附录C中提供了标准化链路层类型代码列表。
        (4)SnapLen: 从每个数据包中转储的最大字节数。每个数据包中超过此值的部分将不会存储在文件中。
         (TODO: Is there a need to signal "no limit"?)
        (5)Options: 可选项，可以提供选项列表（根据第 2.5 节中定义的规则格式化）。
    Interface ID: 写入/读取捕获文件的工具将一个渐进的 16 位数字（从“0”开始）关联到每个接口定义块
    (Interface Definition Block)。这个编号在每个Section中都是唯一的，并且唯一标识了接口（当前Section内部）；
    因此，两个Section可以具有由相同标识符标识的接口。该唯一标识符被其他块（例如数据包块）引用以指出该块所引用的接口
    （例如用于捕获数据包的接口）。 (TODO - It would be nice, to have a "invalid Interface ID" defined,
    e.g. 0xFFFFFFFF)

    除了第2.5节中定义的选项之外，以下选项在此块中有效：

    Name           Code    Length     Description                            Example(s)
    if_name        2       Variable   用于捕获数据的设备名称(UTF-8字符串)。 "eth0" / "\Device\\NPF_{AD1CE675-96D0-47C5-ADD0-2504B9126B68}" / ...
    if_description 3       Variable   用于捕获数据的设备描述(UTF-8字符串)。 "Broadcom NetXtreme" / "First Ethernet Interface" / ...
    if_IPv4addr    4       8          IF地址和掩码。当多个IPv4地址分配给一个IF时，可以在同一Interface Description Block中多次重复此选项。
                                                                             192 168 1 1 255 255 255 0
    if_IPv6addr    5       17         接口网络地址和前缀长度（存储在最后一个字节中）。当一个IF分配了多个IPv6地址时，可以在同一接口描述块中多次重复此选项。
                                                                             2001:0db8:85a3:08d3:1319:8a2e:0370:7344/64 is written (in hex) as "20 01 0d b8 85 a3 08 d3 13 19 8a 2e 03 70 73 44 40"
    if_MACaddr     6       6          接口硬件MAC地址(48 bits).	             00 01 02 03 04 05
    if_EUIaddr     7       8          接口硬件EUI地址（64 位），如果可用。	 TODO: give a good example
    if_speed       8       8          接口速度（以bps为单位）。              100000000 for 100Mbps
    if_tsresol     9       1          如果最高位等于 0，则其余位将时间戳的分辨率表示为 10 的负幂（例如，6 表示微秒分辨率
                                      ，时间戳是自 1970 年 1 月 1 日以来的微秒数）。如果最高位等于 1，则其余位表示分辨率
                                      为2的负幂（例如，10 表示 1/1024 秒）。如果此选项不存在，则假定分辨率为 10^-6（即
                                      时间戳具有与标准“libpcap”时间戳相同的分辨率）。
                                                                             6
    if_tzone       10      4          GMT支持的时区(TODO: specify better).	TODO: give a good example
    if_filter      11      variable   用于捕获流量的过滤器（e.g. "capture only TCP traffic"）。Option Data 的第一个字节
                                      保留了所用过滤器的代码(e.g. if this is a libpcap string, or BPF bytecode, and more).
                                      More details about this format will be presented in Appendix XXX (TODO).
                                      (TODO: better use different options for different fields? e.g.
                                      if_filter_pcap, if_filter_bpf, ...)
                                                                             00 "tcp port 23 and host 10.0.0.5"
    if_os          12      variable   一个UTF-8字符串，包含安装此接口的机器的操作系统名称。这可能与部分标题块（第 3.1 节）
                                      可以包含的相同信息不同，因为捕获可以在远程机器上完成。
                                                                             "Windows XP SP2" / "openSUSE 10.2" / ...
    if_fcslen      13      1          一个整型值，用于指定在此接口上应用的FCS的长度. For link layers whose FCS length
                                      can change during time, the Packet Block Flags Word can be used (see Appendix A).
                                                                              4
    if_tsoffset    14      8          一个64位整数值的偏移量，每个数据包的时间戳加上它可以获得数据包的绝对时间戳（以秒为
                                      单位）。如果缺少该选项，则认为数据包中存储的时间戳为绝对时间戳。偏移量的时区可以通
                                      过选项if_tzone指定。 TODO: won't a if_tsoffset_low for fractional second
                                      offsets be useful for highly syncronized capture systems?
                                                                              1234
    """
    _endian = "little"
    _block_total_length = -1
    _link_type = -1
    _reserved = -1
    _snap_length = -1

    def __init__(self, endian):
        self._endian = endian

    def read_from_file_object(self, file_object):
        return_value = False
        try:
            # file_offset + 8 (函数调用前还有个0x00000001头)
            data = file_object.read(4)
            self._block_total_length = int.from_bytes(data, self._endian)
            # file_offset + 10
            data = file_object.read(2)
            self._link_type = int.from_bytes(data, self._endian)
            # file_offset + 12
            data = file_object.read(2)
            self._reserved = int.from_bytes(data, self._endian)
            # file_offset + 16
            data = file_object.read(4)
            self._snap_length = int.from_bytes(data, self._endian)

            block_total_length_with_padding = round_to_4byte(self._block_total_length)
            option_length = block_total_length_with_padding - 16 - 4
            if option_length != 0:
                data_raw_options = file_object.read(option_length)
                po = PcapngOptions(data_raw_options, option_length)
                po.parse_option_for_interface_description_block(self._endian)

            data = file_object.read(4)
            block_total_length_backward = int.from_bytes(data, self._endian)
            print("Block total length backward:%#x" % block_total_length_backward)
            if block_total_length_backward != self._block_total_length:
                print("错误，前后块长度不一致。")
                return False

            return_value = True
        except Exception as e:
            print(e)
        return return_value


class PcapngDecryptionSecretsBlock:
    """
    4.7.  Decryption Secrets Block
   Decryption Secrets Block (DSB)存储（会话）密钥，这些密钥可以对捕获文件中的数据包进行解密。
   这些密钥的格式由Secrets Type定义。
   一个pcapng文件中可以存在多个DSB，但它们应该写在需要这些密钥的数据包块之前。
   The structure of a Decryption Secrets Block is shown in Figure 15.
                           1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    0 |                   Block Type = 0x0000000A                     |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    4 |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |                          Secrets Type                         |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 |                         Secrets Length                        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   16 /                                                               /
      /                          Secrets Data                         /
      /              (variable length, 填充至32位对齐)                /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      /                                                               /
      /                       Options (variable)                      /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      /                       Block Total Length                      /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                 Figure 15: Decryption Secrets Block Format
   The Decryption Secrets Block has the following fields.
   *  Block Type: Decryption Secrets Block 的块类型为10（0x0A）。
   *  Block Total Length: total size of this block, as described in
      Section 3.1.
   *  Secrets Type (32 bits): 描述接下来的Secrets字段格式的无符号整数标识符。
      请求新的 Secrets 类型代码时应通过创建pull请求来更新本文档，如第10.1节
      所述。
   *  Secrets Length (32 bits): 一个无符号整数，表示接下来的Secrets字段的大小
      ，不包含填充字节。
   *  Secrets Data: 包含密钥的二进制数据，填充到32位对齐。
   *  Options: optionally, a list of options (formatted according to the
      rules defined in Section 3.5) can be present. 当前没有专门适用于DSB
      的选项。

   下面是Secrets Types列表
   0x544c534b:
           TLS Key Log. This format is described at NSS Key Log Format
           (https://developer.mozilla.org/en-
           US/docs/Mozilla/Projects/NSS/Key_Log_Format).  Every line
           MUST be properly terminated with either carriage return and
           linefeed ('\r\n') or linefeed ('\n').  Tools MUST be able to
           handle both line endings.
   0x57474b4c:
           WireGuard Key Log. Every line consists of the key type,
           equals sign ('='), and the base64-encoded 32-byte key with
           optional spaces before and in between.  The key type is one
           of LOCAL_STATIC_PRIVATE_KEY, REMOTE_STATIC_PUBLIC_KEY,
           LOCAL_EPHEMERAL_PRIVATE_KEY, or PRESHARED|_KEY.  This matches
           the output of extract-handshakes.sh
           (https://git.zx2c4.com/WireGuard/tree/contrib/examples/
           extract-handshakes/README), which is part of the WireGuard
           (https://www.wireguard.com/) project.  A PRESHARED_KEY line
           is linked to a session matched by a previous
           LOCAL_EPHEMERAL_PRIVATE_KEY line.  Every line MUST be
           properly terminated with either carriage return and linefeed
           ('\r\n') or linefeed ('\n').  Tools MUST be able to handle
           both line endings.
   Warning: LOCAL_STATIC_PRIVATE_KEY and potentially PRESHARED_KEY are
   long-term secrets, users SHOULD only store non-production keys, or
   ensure proper protection of the pcapng file.
   0x5a4e574b:
           ZigBee NWK Key and ZigBee PANID for that network.  Network
           Key as described in the ZigBee Specification
           (https://zigbeealliance.org/) 05-3473-21 (R21) section 4.2.2.
           The NWK Key is a 16 octet binary AES-128 key used to secure
           NWK Level frames within a single PAN.  The NWK key is
           immediately followed by the 2 octet (16 bit) network PANID in
           little endian format.  If and when the NWK Key changes a new
           DSB will contain the new NWK Key.
   0x5a415053:
           ZigBee APS Key. Application Support Link Key as described in
           the ZigBee Specification (https://zigbeealliance.org/)
           05-3473-21 (R21) section 4.4.  Each 16 octet binary AES-128
           key secures frames exchanged between a pair of network nodes.
           The APS Key is immediately followed by the 2 octet (16 bit)
           network PANID in little endian format.  The PANID is followed
           by the 2 octet (16 bit) short addresses, in little endian
           format, of the nodes to which the APS Key applies.  The
           numerically lower short address shall come first.  There is a
           APS Key DSB for each node pair for which the Link Key is
           known.  As new links are formed, new DSBs contain the new
           Keys.  If the APS Key changes for an existing link, it is
           contained in a new DSB with the new APS Key.

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +---------------------------------------------------------------+
    0 |                   Block Type = 0x0000000A                     |
      +---------------------------------------------------------------+
    4 |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |                  Secrets Type = 0x5a4e574b                    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 |                         Secrets Length                        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   16 |                            AES-128                            |
      |                            NKW Key                            |
      |                          (16 octets)                          |
      |                           (128 bits)                          |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   32 |          PAN ID               |           padding (0)         |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   36 /                                                               /
      /                       Options (variable)                      /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      /                       Block Total Length                      /
      +---------------------------------------------------------------+
                   Figure 16: ZigBee NWK Key Data Format

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +---------------------------------------------------------------+
    0 |                   Block Type = 0x0000000A                     |
      +---------------------------------------------------------------+
    4 |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |                  Secrets Type = 0x5a415053                    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 |                         Secrets Length                        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   16 |                            AES-128                            |
      |                            APS Key                            |
      |                          (16 octets)                          |
      |                           (128 bits)                          |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   32 |           PAN ID              |     Low Node Short Address    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   36 |    High Node Short Address    |         padding (0)           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   40 /                                                               /
      /                       Options (variable)                      /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      /                       Block Total Length                      /
      +---------------------------------------------------------------+
                   Figure 17: ZigBee APS Key Data Format

    """
    _endian = "little"
    _block_total_length = -1
    _secret_type = None
    _secret_length = 0
    _secret_type_name = {0x544c534b: "TLS Key Log", 0x57474b4c: "WireGuard Key Log",
                         0x5a4e574b: "ZigBee NWK Key and ZigBee PANID for that network.",
                         0x5a415053: "ZigBee APS Key."}
    def __init__(self, endian):
        self._endian = endian

    def get_secret_type(self, type_id):
        if type_id in self._secret_type_name:
            return self._secret_type_name[type_id]
        else:
            return "{0}".format(type_id)

    def dump_secret_data(self, secret_data_raw, secret_length, secret_type):
        if secret_type not in self._secret_type_name:
            print("Warning, unknown secret type.")
            return
        if 0x544c534b == secret_type:
            print("The value is:\n", str(secret_data_raw, "utf-8"))
        else:
            print(secret_data_raw)

    def read_from_file_object(self, file_object):
        return_value = False
        try:
            # file_offset + 8 (函数调用前还有个0x0000000A头)
            data = file_object.read(4)
            self._block_total_length = int.from_bytes(data, self._endian)
            block_total_length_with_padding = round_to_4byte(self._block_total_length)
            # file_offset + 12
            data = file_object.read(4)
            self._secret_type = int.from_bytes(data, self._endian)
            # file_offset + 16
            data = file_object.read(4)
            self._secret_length = int.from_bytes(data, self._endian)
            secret_length_with_padding = round_to_4byte(self._secret_length)
            # file_offset + 16 + secret_length_with_padding
            secret_data_raw_with_padding = file_object.read(secret_length_with_padding)
            secret_data_raw = secret_data_raw_with_padding[0:self._secret_length]
            print("This is a secret block.")
            print("Length:%d(%d), type:%s." % (self._secret_length, secret_length_with_padding, self.get_secret_type(self._secret_type)))
            self.dump_secret_data(secret_data_raw, self._secret_length, self._secret_type)
            option_length = block_total_length_with_padding - 16 - secret_length_with_padding - 4
            if option_length != 0:
                option_data_raw = file_object.read(option_length)
                print("option:", option_data_raw)
            data = file_object.read(4)
            block_total_length_backward = int.from_bytes(data, self._endian)
            print("Block total length backward:%#x" % block_total_length_backward)
            if block_total_length_backward != self._block_total_length:
                print("错误，前后块长度不一致。")
                return False
            return_value = True
        except Exception as e:
            print(e)
        return return_value

class PcapngEnhancePackageBlock:
    """
    4.3.  Enhanced Packet Block
   Enhanced Packet Block(增强型数据包块EPB) 是用于存储来自网络的数据包的标准容器。
   The Enhanced Packet Block是可选的，因为数据包也可以存储到Simple Packet Block(SPB)
   中,SPB可以加快捕获文件的生成; 又或者可能文件中没有数据包。Enhanced Packet Block
   的格式见Figure 11。
   增强型数据包块是对原始的、现已过时的Packet Block（附录 A）的改进：
   *  它将Interface Identifier存储为32-bit integer value.这用于数据包来自大量接口的
      需求。
   *  与Packet Block（附录 A）不同，捕获系统在此数据包和前一个数据包之间丢弃的数据包
      数量不存储在head中，而是存储在块本身的option中。
                           1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    0 |                    Block Type = 0x00000006                    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    4 |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |                         Interface ID                          |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 |                        Timestamp (High)                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   16 |                        Timestamp (Low)                        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   20 |                    Captured Packet Length                     |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   24 |                    Original Packet Length                     |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   28 /                                                               /
      /                          Packet Data                          /
      /              variable length, padded to 32 bits               /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      /                                                               /
      /                      Options (variable)                       /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                  Figure 11: Enhanced Packet Block Format

   The Enhanced Packet Block has the following fields:
   *  Block Type: The block type of the Enhanced Packet Block is 6.
   *  Block Total Length: total size of this block, as described in
      Section 3.1.
   *  Interface ID (32 bits): 一个无符号值，指定接收或传输此数据包的接口；
      一个正确的接口ID是接口描述块Interface Description Block（在文件的当前
      Section内）中相同编号（参见第 4.2 节）所标识的接口。 接口ID必须是有效
      的，这意味着对应的接口描述块必须存在。
   *  Timestamp (High) and Timestamp (Low): upper 32 bits and lower 32
      bits of a 64-bit timestamp.时间戳是一个 64 位无符号整数，表示自
      1970-01-01 00:00:00 UTC以来经过的时间单位数。 时间单位的精度由此数据包
      引用的Interface Description Block（接口描述块）的“if_tsresol”选项
      （参见图 10）指定。 注意，与libpcap文件格式中的时间戳不同，
      Enhanced Packet Blocks中的时间戳不会保存为两个32位值用于代表自
      1970-01-01 00:00:00 UTC以来经过的秒和微秒。 增强型数据包块
      （Enhanced Packet Blocks）中的时间戳保存为两个32位数字，代表单个64位数
      的高32位和低32位。
   *  Captured Packet Length (32 bits): 一个无符号值，表示从数据包中捕获的字节
      数（即Packet Data字段的长度）。 它将是接口的Original Packet Length(原始数据包长度)
      和快照长度（SnapLen，在图 10 中定义）中的最小值。 该字段的值不包括在
      Packet Data末尾添加的，用于让Packet Data 32位对齐的填充字节。
   *  Original Packet Length (32 bits): 一个无符号值，表示数据包在网络上传输时
      的实际长度。 如果数据包已被捕获过程截断，则它可以与Captured Packet Length
      （捕获的数据包长度）不同。
   *  Packet Data: 来自网络的数据，包括链路层报头。 该字段的实际长度是
      Captured Packet Length（捕获的数据包长度）加上使之32位对齐的填充数据。
      链路层报头的格式取决于接口描述块（Interface Description Block，见第 4.2 节）
      中指定的LinkType字段，并在[LINKTYPES]表中由该格式对应的条目中确定。
   *  Options: optionally, a list of options (formatted according to the
      rules defined in Section 3.5) can be present.
   除在Section 3.5中定义的选项外,接下来的选项在本块中有效：
   +===============+======+========================+===================+
   | Name          | Code | Length                 | Multiple          |
   |               |      |                        | allowed?          |
   +===============+======+========================+===================+
   | epb_flags     | 2    | 4                      | no                |
   +---------------+------+------------------------+-------------------+
   | epb_hash      | 3    | variable, minimum hash | yes               |
   |               |      | type-dependent         |                   |
   +---------------+------+------------------------+-------------------+
   | epb_dropcount | 4    | 8                      | no                |
   +---------------+------+------------------------+-------------------+
   | epb_packetid  | 5    | 8                      | no                |
   +---------------+------+------------------------+-------------------+
   | epb_queue     | 6    | 4                      | no                |
   +---------------+------+------------------------+-------------------+
   | epb_verdict   | 7    | variable, minimum      | yes               |
   |               |      | verdict type-dependent |                   |
   +---------------+------+------------------------+-------------------+
                   Table 4: Enhanced Packet Block Options
   epb_flags:
           epb_flags选项是一个包含链路层信息的32位标志。 可以在第4.3.1节中
           找到有关该标志所有有效值的完整规范。
   Example: '0'.
   epb_hash:
           epb_hash 选项包含数据包的哈希值。 第一个八位字节（algorithm octet）
           指定散列算法，而后面的八位字节包含实际散列，其大小取决于散列算法，
           即来自第一个八位字节中的值。 哈希算法可以是：2s complement
           (algorithm octet = 0, size = XXX)，CRC32（算法octet=2，size=4），
           MD-5（algorithm octet = 3, size = 16), SHA-1 (algorithm octet = 4, size = 20),
           Toeplitz (algorithm octet = 5, size = 4)。 散列仅覆盖数据包，而
           不包含捕获驱动程序添加的head：这提供了在网卡内部计算它的可能性。
           散列允许更容易地比较/合并不同的捕获文件，以及数据采集系统和捕获
           库之间的可靠数据传输。
   Examples: '02 EC 1D 87 97', '03 45 6E C2 17 7C 10 1E 3C 2E 99 6E C2
   9A 3D 50 8E'.
   epb_dropcount:
           epb_dropcount 选项是一个 64 位无符号整数值，指定此数据包与同一
           接口的前一个数据包之间（由接口和操作系统）丢弃的数据包数，对于
           接口的第一个数据包，则表示捕获过程的开始到此数据包之间丢弃的数
           据包数。
   Example: '0'.
   epb_packetid:
           epb_packetid 选项是一个 64 位无符号整数，用于唯一标识数据包。
           如果多个接口看到同一个数据包，并且捕获应用程序有办法将它们关联
           起来，则必须使用相同的epb_packetid值。 以在两个方向上的所有接口
           上捕获数据包的一个路由器为例。 当数据包在入口到达接口 A 时，会
           创建一个EPB条目，TTL递减，并且就在它从接口B出口之前，在跟踪文件
           中创建另一个EPB条目。 在这种情况下，捕获文件中有两个数据包，它们
           不相同，但可以使用epb_packetid将它们关联起来。
   Example: '0'.
   epb_queue:
           epb_queue选项是一个32位无符号整数，用于标识在接口的哪个队列上
           接收到特定数据包。
   Example: '0'.
   epb_verdict:
           epb_verdict 选项存储数据包的判定（verdict）。判定（verdict）表明
           在处理数据包后将对其进行什么操作。例如，防火墙可能会丢弃数据包。
           这个判定（verdict）可以由各种组件设置，即硬件、Linux 的 eBPF TC或
           XDP框架等。第一个八位字节指定判定类型，而后面的字节包含实际判定
           数据，其大小取决于判定类型，即来自第一个字节中的值。判定类型可以是：
           Hardware（type octet = 0，size = variable），Linux_eBPF_TC
           （type octet = 1，size = 8（64 位无符号整数），value = TC_ACT_*，
           定义在Linux pck_cls.h (https ://git.kernel.org/pub/scm/linux/kernel
           /git/torvalds/linux.git/tree/include/uapi/linux/pkt_cls.h) 包含），
           Linux_eBPF_XDP（type octet = 2，size = 8 （64 位无符号整数），value
            = xdp_action 定义在Linux pbf.h包含(https://git.kernel.org/pub/scm/
            linux/kernel/git/torvalds/linux.git/tree/include/ uapi/linux/bpf.h)）。
   Example: '02 00 00 00 00 00 00 00 02' for Linux_eBPF_XDP with verdict
   XDP_PASS.

4.3.1.  Enhanced Packet Block Flags Word
   Enhanced Packet Block Flags Word是一个 32 位值，包含有关数据包的链路层信息。
   这个标志字是一个unsigned 32-bit integer, 使用Section Header Block中定义的
   endianness。 在下表中位0表示32位值的LSB（least significant bit）位31表示MSB
   (most-significant bit)
     各比特的含义如下：
     +========+======================================================+
     | Bit    | Description                                          |
     | Number |                                                      |
     +========+======================================================+
     | 0-1    | Inbound / Outbound packet (00 = information not      |
     |        | available, 01 = inbound, 10 = outbound)              |
     +--------+------------------------------------------------------+
     | 2-4    | Reception type (000 = not specified, 001 = unicast,  |
     |        | 010 = multicast, 011 = broadcast, 100 =              |
     |        | promiscuous).                                        |
     +--------+------------------------------------------------------+
     | 5-8    | FCS长度,以字节为单位(0000表示次信息无效              |
     |        | 这个值覆盖了Interface Description Block中的if_fcslen |
     |        | 选项, and is                                         |
     |        | used with those link layers (e.g.  PPP) where the    |
     |        | length of the FCS can change during time.            |
     +--------+------------------------------------------------------+
     | 9-15   | Reserved (MUST be set to zero).                      |
     +--------+------------------------------------------------------+
     | 16-31  | link-layer-dependent errors (Bit 31 = symbol error,  |
     |        | Bit 30 = preamble error, Bit 29 = Start Frame        |
     |        | Delimiter error, Bit 28 = unaligned frame error, Bit |
     |        | 27 = wrong Inter Frame Gap error, Bit 26 = packet    |
     |        | too short error, Bit 25 = packet too long error, Bit |
     |        | 24 = CRC error, other?? are 16 bit enough?).         |
     +--------+------------------------------------------------------+
                                  Table 5
   NOTE: 注意：在本规范的早期版本中，位0被指定为MSB，位31是32位无符号整数的LSB
   ，而不是位0对应LSB位31对应MSB。 一些实现将位0设定成了LSB，并且没有已知的实现
   将位0设置成MSB，因此对规范进行了更改以迁就这一现实。
    """
    _endian = "little"
    _block_total_length = 0
    _block_total_length_with_padding = 0
    _interface_id = -1
    _timestamp_high = -1
    _timestamp_low = -1
    _captured_packet_length = -1
    _captured_packet_length_with_padding = -1
    _original_packet_length = -1

    def __init__(self, endian):
        self._endian = endian

    def parse_options(self, option_raw_data, opition_real_len):
        if option_raw_data is None or opition_real_len == 0:
            return False
        pnp = {2: "epb_flags", 3: "epb_hash", 4: "epb_dropcount",
               5: "epb_packetid", 6: "epb_queue", 7: "epb_verdict"}
        remain_option_length = opition_real_len
        cursor = 0
        opt_endofopt = 0
        while remain_option_length != 0:
            option_code = int.from_bytes(option_raw_data[cursor: cursor + 2], section_endian)
            if option_code == opt_endofopt:
                print("match end of option list op code.")
                break
            option_length = int.from_bytes(option_raw_data[cursor + 2: cursor + 4], section_endian)
            option_value = option_raw_data[cursor + 4: cursor + 4 + option_length]
            if option_code in pnp:
                print("option:%s, option_length:%d(%d)" % (
                pnp[option_code], option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
                if option_code == 2:
                    option_epb_flag = int.from_bytes(option_value, section_endian)
                    in_out_bound = option_epb_flag & 0x03
                    if in_out_bound == 0x00:
                        print("io bound: information is not available")
                    elif in_out_bound == 0x01:
                        print("io bound: in")
                    else:
                        print("io bound: out")

                    reception_type = option_epb_flag & 0x1C #00011100b
                    #(000 = not specified, 001 = unicast, 010 = multicast, 011 = broadcast, 100 = promiscuous
                    if reception_type == 0:
                        print("reception type: not specified")
                    elif reception_type == 1:
                        print("reception type: unicast")
                    elif reception_type == 2:
                        print("reception type: multicast")
                    elif reception_type == 3:
                        print("reception type: broadcast")
                    elif reception_type == 4:
                        print("reception type: promiscuous")
                    else:
                        print("reception type: unknown")

                    #000111100000b
                    #| 5 - 8 | FCS长度, 以字节为单位(0000表示此信息无效 这个值覆盖了InterfaceDescriptionBlock中的if_fcslen |
                    fcs_len = (option_epb_flag & 0x1E0) >> 5
                    print("FCS length override:", fcs_len)

                    #16-31 link-layer-dependent errors
                    link_layer_dependent_errors = (option_epb_flag & 0xFFFF0000)
                    print("link-layer-dependent errors: %#x"%link_layer_dependent_errors)

                print("option:%d, option_length:%d(%d)" % (option_code, option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
            cursor += (4 + round_to_4byte(option_length))
            remain_option_length = opition_real_len - cursor

    def read_from_file_object(self, file_object):
        return_value = False
        try:
            # file_offset + 8 (函数调用前还有个0x00000006头)
            data = file_object.read(4)
            self._block_total_length = int.from_bytes(data, self._endian)
            self._block_total_length_with_padding = round_to_4byte(self._block_total_length)
            print("length: %d(%d)"%(self._block_total_length, self._block_total_length_with_padding))
            # file_offset + 12
            data = file_object.read(4)
            self._interface_id = int.from_bytes(data, self._endian)
            print("interface id:", self._interface_id)
            # file_offset + 16
            data = file_object.read(4)
            self._timestamp_high = int.from_bytes(data, self._endian)
            # file_offset + 20
            data = file_object.read(4)
            self._timestamp_low = int.from_bytes(data, self._endian)
            print("timestamp with [high:low]: %d:%d"%(self._timestamp_high, self._timestamp_low))
            # file_offset + 24
            data = file_object.read(4)
            self._captured_packet_length = int.from_bytes(data, self._endian)
            self._captured_packet_length_with_padding = round_to_4byte(self._captured_packet_length)
            # file_offset + 28
            data = file_object.read(4)
            self._original_packet_length = int.from_bytes(data, self._endian)
            print("captured packet length: %d, original_packet_length: %d"%(self._captured_packet_length, self._original_packet_length))
            # file_offset + 32
            raw_captured_data_with_padding = file_object.read(self._captured_packet_length_with_padding)
            print("binary data:", raw_captured_data_with_padding[0:self._original_packet_length])
            option_length = self._block_total_length_with_padding - 32 - self._captured_packet_length_with_padding
            if option_length != 0:
                option_data_raw = file_object.read(option_length)
                print("option:", option_data_raw)
            data = file_object.read(4)
            block_total_length_backward = int.from_bytes(data, self._endian)
            print("Block total length backward:%#x" % block_total_length_backward)
            if block_total_length_backward != self._block_total_length:
                print("错误，前后块长度不一致。")
                return False
            return_value = True
        except Exception as e:
            print(e)
        return return_value


class PcapngSimplePacketBlock:
    """
    4.4.  Simple Packet Block

   Simple Packet Block (SPB)是一个轻量级容器，用于存储来自网络的数据包。
   它的存在是可选的。

   Simple Packet Block类似于Enhanced Packet Block（参见第 4.3 节），但
   它更小、更易于处理并且仅包含最少的信息集。 当性能或空间占用是关键因素
   时，例如在持续的流量捕获应用程序中，SPB块优于EPB。 捕获文件可以同时包
   含Simple Packet Block类似于Enhanced Packet Block：例如，当硬件资源变得
   紧张时，捕获工具可以从EPB切换到SPB。

   简单数据包块不包含接口 ID 字段。 因此，必须假设所有Simple Packet Blocks
   都在先前在第一个接口描述块(Interface Description Block)中指定的接口上被捕获。

   Figure 12 shows the format of the Simple Packet Block.

                           1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    0 |                    Block Type = 0x00000003                    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    4 |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |                    Original Packet Length                     |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 /                                                               /
      /                          Packet Data                          /
      /              variable length, padded to 32 bits               /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                   Figure 12: Simple Packet Block Format

   The Simple Packet Block has the following fields:

   *  Block Type: The block type of the Simple Packet Block is 3.

   *  Block Total Length: total size of this block, as described in
      Section 3.1.

   *  Original Packet Length (32 bits): 一个无符号值，表示数据包在网络上
      传输时的实际长度。 如果数据包已被捕获过程截断，则它可以与Packet Data
      字段的长度不同，在这种情况下，第 4.2 节中的SnapLen值将小于这个
      Original Packet Length值，并且SnapLen值必须用于确定数据包数据字段长度
      的大小。

   *  Packet Data: 来自网络的数据，包括链路层报头。 该字段的长度可以从
      Block Header中的Block Total Length字段计算出，它是SnapLen
      （存在于Interface Description Block中）和Original Packet Length
      （存在于该Header中）中的最小值。 此数据包数据字段中的数据格式取决
      于接口描述块（见第 4.2 节）中指定的LinkType字段，并在[LINKTYPES]表
      对应该格式的条目中指定。

   Simple Packet Block简单数据包块不包含时间戳，因为这通常是PC上成本最高的
   操作之一。 此外，有些应用程序不需要它； 例如 入侵检测系统对数据包感兴趣，
   而不是它们的时间戳。

   由于不能引用正确的接口（它不包含任何接口ID字段），因此Simple Packet Block
   不能出现在具有多个接口的Section中。

   Simple Packet Block 在磁盘空间方面非常高效：a snapshot whose length is 100
   octets requires only 16 octets of overhead, which corresponds to an
   efficiency of more than 86%.
    """
    _endian = ""
    _block_total_length = 0
    _block_total_length_with_padding = 0
    _original_packet_length = 0

    def __init__(self, endian):
        self._endian = endian

    def read_from_file_object(self, file_object):
        return_value = False
        try:
            # file_offset + 8 (函数调用前还有个0x00000006头)
            data = file_object.read(4)
            self._block_total_length = int.from_bytes(data, self._endian)
            self._block_total_length_with_padding = round_to_4byte(self._block_total_length)
            print("length: %d(%d)"%(self._block_total_length, self._block_total_length_with_padding))

            # file_offset + 12
            data = file_object.read(4)
            self._original_packet_length = int.from_bytes(data, self._endian)
            print("original packet length: %d" % self._original_packet_length)

            packet_data_len = self._block_total_length_with_padding - 12
            raw_packet_data = file_object.read(packet_data_len)
            print("packet data length:", packet_data_len)
            print("packet data:", raw_packet_data)

            data = file_object.read(4)
            block_total_length_backward = int.from_bytes(data, self._endian)
            print("Block total length backward:%#x" % block_total_length_backward)
            if block_total_length_backward != self._block_total_length:
                print("错误，前后块长度不一致。")
                return False
            return_value = True
        except Exception as e:
            print(e)
        return return_value


class PcapngNameResolveBlock:
    """
    4.5.  Name Resolution Block

   Name Resolution Block (NRB)  用于支持数字地址（存在于捕获的数据包中）
   与其对应的规范名称的关联，它是可选的。 将文字名称保存在文件中可以满足稍后
   执行名称解析的需要，因为当前名称和地址之间的关联可能与捕获时对应的关联不同。
   此外，NRB避免了每次打开跟踪捕获时发出大量DNS请求的麻烦，并且还给在使用未
   连接到网络的机器上读取捕获时还提供名称解析（的便利）。

   Name Resolution Block 通常放置在文件的开头，但不能对其位置进行假设。 一个
   pcapng文件中可以存在多个 NRB，要么是由于内存限制，要么是因为文件处理工具
   （如网络分析器）执行了额外的名称解析。

   Name Resolution Block不需要包含任何记录，除了必须是最后一个记录的
   nrb_record_end记录。 NRB记录中的地址和姓名可以重复多次； 即同一个IP地址可以
   解析为多个名称，同一个名称可以解析为多个IP地址，甚至同一个“地址-名称”对
   可能在同一个NRB中或跨NRB出现多次。

   The format of the Name Resolution Block is shown in Figure 13.

                           1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    0 |                    Block Type = 0x00000004                    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    4 |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |      Record Type              |      Record Value Length      |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 /                       Record Value                            /
      /              variable length, padded to 32 bits               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      .                                                               .
      .                  . . . other records . . .                    .
      .                                                               .
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |  Record Type = nrb_record_end |   Record Value Length = 0     |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      /                                                               /
      /                      Options (variable)                       /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                  Figure 13: Name Resolution Block Format

   The Name Resolution Block has the following fields:

   *  Block Type: The block type of the Name Resolution Block is 4.

   *  Block Total Length: total size of this block, as described in
      Section 3.1.

    在这之后跟着零个或多个名称解析记录（TLV 格式），每个记录包含网络地址和名称
    之间的关联。 nrb_record_end必须添加在最后一个 Record之后，并且即使NRB中没有
    其他Records也必须存在。 目前有三种可能的记录类型：
                  +=================+========+==========+
                  | Name            | Code   | Length   |
                  +=================+========+==========+
                  | nrb_record_end  | 0x0000 | 0        |
                  +-----------------+--------+----------+
                  | nrb_record_ipv4 | 0x0001 | variable |
                  +-----------------+--------+----------+
                  | nrb_record_ipv6 | 0x0002 | variable |
                  +-----------------+--------+----------+
                   Table 6: Name Resolution Block Records
   nrb_record_end:
           nrb_record_end 记录分隔名称解析记录的结尾。 需要此记录来确定名称解析
           记录列表何时结束以及一些Options选项（如果有）何时开始。

   nrb_record_ipv4:
           nrb_record_ipv4记录指定一个IPv4地址（包含在前4个字节中），后跟一个或
           多个包含该地址的DNS条目的以零结尾的 UTF-8 字符串。 因此，此记录类型的
           最小有效记录长度为6：4（IP）、1 个字符和一个零值八位字节终止符。 请
           注意，IP地址被视为四个独立的字节，IP 地址的每个字节都是一个单位；它
           不是32位字，因此SHB的字节序不会影响该字段的值。

   Example: '127 0 0 1'"localhost".

   [Open issue: is an empty string (i.e., just a zero-value octet)
   valid?]

   nrb_record_ipv6:
           nrb_record_ipv6 记录指定一个IPv6地址（包含在前16个字节中），后跟一个
           或多个包含该地址的DNS条目的以零结尾的字符串。 因此，此记录类型的最小
           有效记录长度为18：IP为16字节、1 个字符和一个零值字节终止符。

   Example: '20 01 0d b8 00 00 00 00 00 00 00 00 12 34 56 78'"somehost".

   [Open issue: is an empty string (i.e., just a zero-value octet)
   valid?]

   那些在之前指定的记录类型以外的记录类型必须忽略并跳过，以避免破坏向后兼容性。
   因为将来可能会定义更多记录类型。

   每个Record Value都填充至与32位边界对齐。 对应的Record Value Length反映了
   Record Value的实际长度； 它不包括Record Type的长度、Record Value Length
   字段、Record Value的任何填充或Record Value之后的任何内容。 对于具有名称
   字符串的Record Types，Record Length确实包括终止该字符串的零值字节。 除非
   另有说明，否则记录长度为0是有效的。

   在名称解析记录列表之后，可以有一个选项列表（格式见第3.5节中定义的规则）。

   除了第3.5节中定义的选项外，以下选项在此块中有效：

          +===============+======+==========+===================+
          | Name          | Code | Length   | Multiple allowed? |
          +===============+======+==========+===================+
          | ns_dnsname    | 2    | variable | no                |
          +---------------+------+----------+-------------------+
          | ns_dnsIP4addr | 3    | 4        | no                |
          +---------------+------+----------+-------------------+
          | ns_dnsIP6addr | 4    | 16       | no                |
          +---------------+------+----------+-------------------+

                   Table 7: Name Resolution Block Options

   ns_dnsname:
           ns_dnsname 选项是一个UTF-8字符串，其中包含用于执行名称解析的机器
           （DNS服务器）的名称。 该字符串不是以零结尾的。

   Example: "our_nameserver".

   ns_dnsIP4addr:
           ns_dnsIP4addr选项指定DNS服务器的IPv4地址。 请注意，IP地址被视为
           四个八位字节，IP地址的每个字节是一个单位； 它不是32位字，因此
           SHB的字节序不会影响该字段的值。

   Example: '192 168 0 1'.

   ns_dnsIP6addr:
           ns_dnsIP6addr选项指定DNS服务器的IPv6 地址。

   Example: '20 01 0d b8 00 00 00 00 00 00 00 00 12 34 56 78'.
    """

    _endian = ""
    _block_total_length = 0
    _block_total_length_with_padding = 0

    def __init__(self, endian):
        self._endian = endian

    def parse_options(self, option_raw_data, opition_real_len):
        if option_raw_data is None or opition_real_len == 0:
            return False
        pnp = {2: "ns_dnsname", 3: "ns_dnsIP4addr", 4: "ns_dnsIP6addr"}
        remain_option_length = opition_real_len
        cursor = 0
        opt_endofopt = 0
        while remain_option_length != 0:
            option_code = int.from_bytes(option_raw_data[cursor: cursor + 2], section_endian)
            if option_code == opt_endofopt:
                print("match end of option list op code.")
                break
            option_length = int.from_bytes(option_raw_data[cursor + 2: cursor + 4], section_endian)
            option_value = option_raw_data[cursor + 4: cursor + 4 + option_length]
            if option_code in pnp:
                #todo: 在这里解析并打印各option的值。
                print("option:%s, option_length:%d(%d)" % (
                pnp[option_code], option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
            else:
                print("option:%d, option_length:%d(%d)" % (option_code, option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
            cursor += (4 + round_to_4byte(option_length))
            remain_option_length = opition_real_len - cursor

    def read_from_file_object(self, file_object):
        return_value = False
        try:
            data = file_object.read(4)
            self._block_total_length = int.from_bytes(data, self._endian)
            self._block_total_length_with_padding = round_to_4byte(self._block_total_length)
            print("length: %d(%d)" % (self._block_total_length, self._block_total_length_with_padding))
            record_type = int.from_bytes(file_object.read(2), self._endian)
            record_value_length = int.from_bytes(file_object.read(2), self._endian)
            file_offset = 4 + 4 + 4
            while record_type != 0x0000:
                record_value_length_with_padding = round_to_4byte(record_value_length)
                raw_record_value_with_padding = file_object.read(record_value_length_with_padding)
                if 0x0001 == record_type:
                    print("record type nrb_record_ipv4")
                elif 0x0002 == record_type:
                    print("record type nrb_record_ipv6")
                else:
                    print("record type UNKNOWN")
                #todo:在这里根据不同的记录类型解析出IP和名字并打印内容。
                print("data:", raw_record_value_with_padding[0:record_value_length])
                record_type = int.from_bytes(file_object.read(2), self._endian)
                record_value_length = int.from_bytes(file_object.read(2), self._endian)
                file_offset = file_offset + raw_record_value_with_padding + 4

            options_length = self._block_total_length_with_padding - file_offset - 4
            if options_length != 0:
                options_raw_data = file_object.read(options_length)
                self.parse_options(options_raw_data, options_length)
            block_total_length_backward = int.from_bytes(file_object.read(4), self._endian)
            if block_total_length != block_total_length_backward:
                print("错误，前后块长度不一致。")
                return False
            return_value = True
        except Exception as e:
            print(e)
        return return_value


class PcapngInterfaceStasticsBlock:
    """
    4.6.  Interface Statistics Block

   Interface Statistics Block (ISB)包含给定接口的捕获统计数据，它是可选的。
   统计数据和当前section中定义的接口关联，由接口ID字段标识。 Interface Statistics Block
   通常放置在文件的末尾，但不能对其位置进行假设 - 它甚至可以针对同一接口多次出现。

   The format of the Interface Statistics Block is shown in Figure 14.

                           1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    0 |                   Block Type = 0x00000005                     |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    4 |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |                         Interface ID                          |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 |                        Timestamp (High)                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   16 |                        Timestamp (Low)                        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   20 /                                                               /
      /                      Options (variable)                       /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                Figure 14: Interface Statistics Block Format

   The fields have the following meaning:

   *  Block Type: The block type of the Interface Statistics Block is 5.

   *  Block Total Length: total size of this block, as described in
      Section 3.1.

   *  Interface ID: 指定这些统计信息所关联的接口； 正确的接口将是其
   Interface Description Block（在文件的当前Section内）由该字段的相同编号
   （参见第 4.2 节）标识的接口。

   *  Timestamp: 此统计数据所指的时间。 时间戳的格式与Enhanced Packet Block
    （第 4.3 节）中已经定义的相同； 时间单位的精度由此数据包参考的
      Interface Description Block的“if_tsresol”选项（参见图 10）指定。

   *  Options: optionally, a list of options (formatted according to the
      rules defined in Section 3.5) can be present.

   所有统计字段都定义为选项，以应对没有完整统计集的系统使用。 因此，除了
   第 3.5 节中定义的选项外，以下选项在此块中有效：

         +==================+======+========+===================+
         | Name             | Code | Length | Multiple allowed? |
         +==================+======+========+===================+
         | isb_starttime    | 2    | 8      | no                |
         +------------------+------+--------+-------------------+
         | isb_endtime      | 3    | 8      | no                |
         +------------------+------+--------+-------------------+
         | isb_ifrecv       | 4    | 8      | no                |
         +------------------+------+--------+-------------------+
         | isb_ifdrop       | 5    | 8      | no                |
         +------------------+------+--------+-------------------+
         | isb_filteraccept | 6    | 8      | no                |
         +------------------+------+--------+-------------------+
         | isb_osdrop       | 7    | 8      | no                |
         +------------------+------+--------+-------------------+
         | isb_usrdeliv     | 8    | 8      | no                |
         +------------------+------+--------+-------------------+

               Table 8: Interface Statistics Block Options

   isb_starttime:
           isb_starttime 选项指定捕获开始的时间； 时间将存储在两块中，每块四字节。
           时间戳的格式与Enhanced Packet Block（第 4.3 节）中定义的格式相同； 时间
           单位的精度由此数据包引用的Interface Description Block的“if_tsresol”
           选项（参见图 10）指定。

   Example: '96 c3 04 00 73 89 6a 65', in Little Endian, decodes to
   2012-06-29 06:17:00.834163 UTC.

   isb_endtime:
           isb_endtime 选项指定捕获结束的时间； 时间将存储在两块中，每块四字节。
           时间戳的格式与Enhanced Packet Block（第 4.3 节）中定义的格式相同； 时间单位
           的精度由此数据包引用的Interface Description Block的“if_tsresol”选项（参见图 10）指定。

   Example: '97 c3 04 00 aa 47 ca 64', in Little Endian, decodes to
   2012-06-29 07:28:25.298858 UTC.

   isb_ifrecv:
           isb_ifrecv选项为64位无符号整数，表示捕获开始后从物理接口收到的数据包数。

   Example: the decimal number 100.

   isb_ifdrop:
           isb_ifdrop选项为64 位无符号整数，表示从捕获开始后由于缺少资源而被接口
           丢弃的数据包数。

   Example: '0'.

   isb_filteraccept:
           isb_filteraccept选项为64位无符号整数，表示捕获开始后从过滤器处接收的
           数据包数。

   Example: the decimal number 100.

   isb_osdrop:
           isb_osdrop选项为64位无符号整数，表示操作系统从捕获开始后丢弃的数据包数。

   Example: '0'.

   isb_usrdeliv:
           isb_usrdeliv 选项为64 位无符号整数，表示从捕获开始后传递给用户的数据包数。
           此字段的值可能与“isb_filteraccept - isb_osdrop”的结果不同，因为某些数据包可能在
           捕获结束时仍在操作系统缓冲区中。

   Example: '0'.

   所有与数据包计数器有关的字段都是 64 位值，用当前部分的字节序表示。 访问这些字段时必须特别小心：
   由于所有块都与32位边界对齐，因此不能保证这些字段在 64 位边界上对齐。
    """

    _endian = ""
    _block_total_length = 0
    _block_total_length_with_padding = 0
    _interface_id = 0
    _timestamp_high = 0
    _timestamp_low = 0
    raw_options_data_length = 0
    _raw_options_data = None

    def __init__(self, endian):
        self._endian = endian

    def parse_options(self):
        if 0 == self.raw_options_data_length or self._raw_options_data is None:
            return False
        pnp = {2: "isb_starttime", 3: "isb_endtime", 4: "isb_ifrecv", 5: "isb_ifdrop", 6: "isb_filteraccept",
               7: "isb_osdrop", 8: "isb_usrdeliv"}
        remain_option_length = self.raw_options_data_length
        cursor = 0
        opt_endofopt = 0
        while remain_option_length != 0:
            option_code = int.from_bytes(self._raw_options_data[cursor: cursor + 2], section_endian)
            if option_code == opt_endofopt:
                print("match end of option list op code.")
                break
            option_length = int.from_bytes(self._raw_options_data[cursor + 2: cursor + 4], section_endian)
            option_value = self._raw_options_data[cursor + 4: cursor + 4 + option_length]
            if option_code in pnp:
                #todo: 在这里解析并打印各option的值。如下if 2 == option_code:的代码块所示：
                if 2 == option_code:
                    if option_length != 8:
                        raise Exception("bad length of option isb_starttime")
                    start_time_high = int.from_bytes(option_value[0:4], section_endian)
                    start_time_low = int.from_bytes(option_value[4:8], section_endian)
                    print("option isb_starttime, (high:low) %d:%d"%(start_time_high, start_time_low))

                print("option:%s, option_length:%d(%d)" % (
                pnp[option_code], option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
            else:
                print("option:%d, option_length:%d(%d)" % (option_code, option_length, round_to_4byte(option_length)),
                      "option_value:", option_value)
            cursor += (4 + round_to_4byte(option_length))
            remain_option_length = self.raw_options_data_length - cursor

    def read_from_file_object(self, file_object):
        return_value = False
        try:
            data = file_object.read(4)
            self._block_total_length = int.from_bytes(data, self._endian)
            self._block_total_length_with_padding = round_to_4byte(self._block_total_length)

            self._interface_id = int.from_bytes(file_object.read(4), self._endian)
            self._timestamp_high = int.from_bytes(file_object.read(4), self._endian)
            self._timestamp_low = int.from_bytes(file_object.read(4), self._endian)

            self.raw_options_data_length = self._block_total_length_with_padding - 24
            self._raw_options_data = file_object.read(self.raw_options_data_length)
            self.parse_options()

            block_total_length_backward = int.from_bytes(file_object.read(4), self._endian)
            if block_total_length != block_total_length_backward:
                print("错误，前后块长度不一致。")
                return False
            return_value = True
        except Exception as e:
            print(e)
        return return_value


class PcapngCustomBlock:
    """
    4.8.  Custom Block

   Custom Block (CB) 是用于存储不属于另一个块的自定义数据的容器； 要将自定义数据
   存储为另一个块的一部分，请参阅第 3.5.1 节。 Custom Block是可选的，可以重复任意
   次数，并且可以出现在任何其他块之前或之后，除了必须在文件中首先出现的第一个
   Section Header Block。可以在同一个 pcapng 文件中使用不同类型代码和/或不同
   Private Enterprise Numbers的Custom Block。

   The format of a Custom Block is shown in Figure 18.
                           1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    0 |             Block Type = 0x00000BAD or 0x40000BAD             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    4 |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    8 |                Private Enterprise Number (PEN)                |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   12 /                                                               /
      /                          Custom Data                          /
      /              variable length, padded to 32 bits               /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      /                                                               /
      /                      Options (variable)                       /
      /                                                               /
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                      Block Total Length                       |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                       Figure 18: Custom Block Format

   Custom Block使用类型代码 0x00000BAD（十进制 2989）作为自定义块，pcapng 重写者可以
   将其复制到新文件中，类型代码 0x40000BAD（十进制为 1073744813）用于不应复制的块。
   有关详细信息，请参阅第 5.2 节。

   The Custom Block has the following fields:

   *  Block Type: The block type of the Custom Block is 0x00000BAD or
      0x40000BAD, as described previously.

   *  Block Total Length: total size of this block, as described in
      Section 3.1.

   *  Private Enterprise Number (32 bits): IANA 分配的私营企业编号，用于标识
   那个定义此Custom Block的组织。 有关详细信息，请参阅第 5.1 节。 PEN 必须使用
   与它在其范围内的Section Header Block相同的字节序进行编码。

   *  Custom Data: the custom data, padded to a 32 bit boundary.

   *  Options: optionally, a list of options (formatted according to the
      rules defined in Section 3.5) can be present.  Note that custom
      options for the Custom Block still use the custom option format
      and type code, as described in Section 3.5.1.
    """
    _endian = ""
    _block_total_length = 0
    _block_total_length_with_padding = 0
    _private_enterprise_number = -1

    def __init__(self, endian):
        self._endian = endian

    def read_from_file_object(self, file_object):
        return_value = False
        try:
            data = file_object.read(4)
            self._block_total_length = int.from_bytes(data, self._endian)
            self._block_total_length_with_padding = round_to_4byte(self._block_total_length)
            self._private_enterprise_number = int.from_bytes(file_object.read(4), self._endian)
            print("private enterprise number:", self._private_enterprise_number)
            raw_custom_data_and_options_length = self._block_total_length_with_padding - 4 - 4 - 4 - 4
            raw_custom_data_and_options = file_object.read(raw_custom_data_and_options_length)
            # todo: parse custom data and options.
            block_total_length_backward = int.from_bytes(file_object.read(4), self._endian)
            if block_total_length != block_total_length_backward:
                print("错误，前后块长度不一致。")
                return False
            return_value = True
        except Exception as e:
            print(e)
        return return_value


if __name__ == '__main__':
    parser = OptionParser(usage="%prog [options]")
    parser.add_option("-f", "--file", action="store", type="string", dest="file_to_parse", help=u"指定一个需要解析的文件（需要全路径）")
    (options, args) = parser.parse_args()
    if options.file_to_parse is None:
        print("请使用-f或者--file=指定一个需要解析的文件（需要全路径）。")
    else:
        with open(options.file_to_parse, "rb") as f:
            data = f.read(4)
            # 一般而言 big: 网络字节序, little:主机字节序
            # 0x0A0D0D0A是回文,所以下面这句传入big or little都无所谓。
            block_type = int.from_bytes(data, "big")
            if block_type != BlockTypeCode.BTS_SectionHeaderBlock.value:
                f.close()
                exit(1)

            a = PcapngSecionHeaderBlock()
            result = a.read_from_file_object(f)
            print("parse pcapng section header block:", result)
            section_endian = a.get_endian()

            eof = False
            while not eof:
                print("-------------------------------------")
                data = f.read(4)
                block_type = int.from_bytes(data, section_endian)
                if BlockTypeCode.BTS_InterfaceDescriptionBlock.value == block_type:
                    b = PcapngInterfaceDescriptionBlock(section_endian)
                    result = b.read_from_file_object(f)
                    print("parse pcapng interface description block:", result)
                elif BlockTypeCode.BTS_DecryptionSecretsBlock.value == block_type:
                    c = PcapngDecryptionSecretsBlock(section_endian)
                    result = c.read_from_file_object(f)
                    print("parse pcapng Decryption Secrets Block:", result)
                elif BlockTypeCode.BTS_EnhancedPacketBlock.value == block_type:
                    d = PcapngEnhancePackageBlock(section_endian)
                    result = d.read_from_file_object(f)
                    print("parse pcapng Enhance packet block:", result)
                elif BlockTypeCode.BTS_SimplePacketBlock.value == block_type:
                    e = PcapngSimplePacketBlock(section_endian)
                    result = e.read_from_file_object(f)
                    print("parse pcapng Simple packet block:", result)
                elif BlockTypeCode.BTS_NameResolutionBlock.value == block_type:
                    g = PcapngNameResolveBlock(section_endian)
                    result = g.read_from_file_object(f)
                    print("parse pcapng Name Resolve Block:", result)
                elif BlockTypeCode.BTS_InterfaceStatisticsBlock.value == block_type:
                    h = PcapngInterfaceStasticsBlock(section_endian)
                    result = h.read_from_file_object(f)
                    print("parse pcapng Interface Stastics Block:", result)
                elif BlockTypeCode.BTS_CustomBlockWhichCanBeCopied.value == block_type \
                        or BlockTypeCode.BTS_CustomBlockWhichShouldNotBeCopied.value == block_type:
                    i = PcapngCustomBlock(section_endian)
                    result = i.read_from_file_object(f)
                    print("parse pcapng Custom Block:", result)
                else:
                    data = f.read(4)
                    block_total_length = int.from_bytes(data, section_endian)
                    print("block type: %d, length: %d"%(block_type, block_total_length))
                    if block_total_length == 0:
                        print("read zero length block. break.")
                        break
                    block_total_length_with_padding = round_to_4byte(block_total_length)
                    data = f.read(block_total_length_with_padding - 4)

                if not result:
                    break
            f.close()
